package com.personal.system.service.impl.sys;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.personal.common.result.Result;
import com.personal.common.utils.JwtHelper;
import com.personal.model.system.SysRole;
import com.personal.model.system.SysUser;
import com.personal.model.system.SysUserRole;
import com.personal.model.vo.LoginVo;
import com.personal.model.vo.RouterVo;
import com.personal.model.vo.SysUserQueryVo;
import com.personal.system.config.SecurityUtil;
import com.personal.system.constant.Constants;
import com.personal.system.custom.CustomUser;
import com.personal.system.entity.vo.EmailLoginDTO;
import com.personal.system.entity.vo.UserInfoVO;
import com.personal.system.exception.GlobalException;
import com.personal.system.mapper.sys.SysUserMapper;
import com.personal.system.mapper.sys.SysUserRoleMapper;
import com.personal.system.service.LoginLogService;
import com.personal.system.service.sys.SysMenuService;
import com.personal.system.service.sys.SysRoleService;
import com.personal.system.service.sys.SysUserService;
import com.xxl.job.core.util.IpUtil;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author personal
 * @since 2022-09-28
 */
@Slf4j
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

    @Resource
    private SysMenuService sysMenuService;
    @Resource
    private SysRoleService sysRoleService;

    @Resource
    private SysUserRoleMapper userRoleMapper;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private LoginLogService loginLogService;

    @Resource
    private SecurityUtil securityUtil;

    // 用户列表 异步会产生循环依赖，在异常类中加 @Lazy 懒加载
    @Override
    // @Async("asyncExecutor") // 异步会丢失线程ID,要指定线程池,将父线程的trackId传递下去给子线程即可
    public IPage<SysUser> selectPage(Page<SysUser> pageParam, SysUserQueryVo sysUserQueryVo) {

        return baseMapper.selectPage(pageParam, sysUserQueryVo);
    }

    @Override
    public List<SysUser> selectList(SysUserQueryVo sysUserQueryVo) {

        LambdaQueryWrapper<SysUser> qw = new LambdaQueryWrapper<>();
        String keyword = sysUserQueryVo.getKeyword();
        if (StrUtil.isNotBlank(keyword)) {
            qw.like(SysUser::getUsername, keyword);
            qw.or().like(SysUser::getName, keyword);
            qw.or().like(SysUser::getPhone, keyword);
        }
        String createTimeBegin = sysUserQueryVo.getCreateTimeBegin();
        String createTimeEnd = sysUserQueryVo.getCreateTimeEnd();
        if (StrUtil.isNotBlank(createTimeBegin) && StrUtil.isNotBlank(createTimeEnd)) {
            qw.ge(SysUser::getCreateTime, createTimeBegin);
            qw.le(SysUser::getCreateTime, createTimeEnd + " 23:59:59");
        }
        qw.orderByDesc(SysUser::getCreateTime);

        List<SysUser> sysUsers = baseMapper.selectList(qw);

        return sysUsers;
    }

    //更改用户状态
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
    public void updateStatus(String id, Integer status) {
        long start = System.currentTimeMillis();
        //根据用户id查询
        SysUser sysUser = baseMapper.selectById(id);
        sysUser.setUpdateTime(new Date());
        //设置修改状态
        sysUser.setStatus(status);
        //调用方法修改
        baseMapper.updateById(sysUser);
        long end = System.currentTimeMillis();
        log.info("修改用时》》》" + ((end - start) / 1000));
    }

    //username查询
    @Override
    public Object getToken(LoginVo loginVo) {

        String key = "captcha_codes:" + loginVo.getUuid();
        Object code = redisTemplate.opsForValue().get(key);
        if (null == code) throw new GlobalException(400, "验证码为空!请刷新下");
        String rCode = code.toString();
        if (!rCode.equals(loginVo.getCode())) throw new GlobalException(404, "验证码不正确！");

        Authentication authenticate;
        try {
            authenticate = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword()));
        } catch (AuthenticationException e) {
            if (e instanceof BadCredentialsException) {
                throw new GlobalException(404, "用户或密码不匹配！");
            } else {
                throw new GlobalException(500, e.getMessage());
            }
        }
        redisTemplate.delete(key);

        CustomUser customUser = (CustomUser) authenticate.getPrincipal();
        SysUser sysUser = customUser.getSysUser();
        List<SysUserRole> userRoles = userRoleMapper.selectList(Wrappers.lambdaQuery(SysUserRole.class).eq(SysUserRole::getUserId, sysUser.getId()));
        List<String> roleIds = userRoles.stream().map(e -> e.getRoleId()).collect(Collectors.toList());
        List<SysRole> roleList = sysRoleService.list(Wrappers.lambdaQuery(SysRole.class).in(SysRole::getId, roleIds));
        sysUser.setRoleList(roleList);
        redisTemplate.opsForValue()
                .set(Constants.LOGIN_USER_KEY + customUser.getUsername(), JSON.toJSONString(customUser), 60, TimeUnit.MINUTES);

        loginLogService.recordLoginLog(sysUser.getUsername(), 1, "127.0.0.1", "登录成功");

        //根据userid和username生成token字符串，通过map返回
        String token = JwtHelper.createToken(sysUser.getId().toString(), sysUser.getUsername());

        Map<String, Object> map = new HashMap<>();
        map.put("token", token);

        return map;
    }

    //根据用户名称获取用户信息（基本信息 和 菜单权限 和 按钮权限数据）
    @Override
    public Map<String, Object> getUserInfo(String username) {
        //根据username查询用户基本信息
        QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        SysUser sysUser = baseMapper.selectOne(wrapper);
        //根据userid查询菜单权限值
        List<RouterVo> routerList = sysMenuService.getUserMenuList(sysUser.getId());
        //根据userid查询按钮权限值
        List<String> permsList = sysMenuService.getUserButtonList(sysUser.getId());
        // 所属角色
        Map<String, Object> roles = sysRoleService.getRolesByUserId(sysUser.getId());
        List<String> userRoleIds = (List<String>) roles.get("userRoleIds");

        Map<String, Object> result = new HashMap<>();
        result.put("name", username);
        result.put("avatar", "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
//        result.put("roles", "[\"admin\"]");
        result.put("roles", userRoleIds);
        //菜单权限数据
        result.put("routers", routerList);
        //按钮权限数据
        result.put("buttons", permsList);
        return result;
    }

    @Override
    public SysUser getUserInfoByUserName(String username) {
        LambdaQueryWrapper<SysUser> qw = new LambdaQueryWrapper<>();
        qw.eq(SysUser::getUsername, username);

        return baseMapper.selectOne(qw);
    }

    @Override
    public void logout(HttpServletRequest request) {
        String username = JwtHelper.getUser(request.getHeader("token")).get("username").toString();
        redisTemplate.delete(Constants.LOGIN_USER_KEY + username);
    }

    ///////////////////////////////////////WEB登陆///////////////////////////////////////////////////////


    @Override
    public Result emailLogin(EmailLoginDTO vo) {
        Authentication authenticate;
        try {
            authenticate = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(vo.getEmail(), vo.getPassword()));
        } catch (AuthenticationException e) {
            if (e instanceof BadCredentialsException) {
                throw new GlobalException(404, "用户或密码不匹配！");
            } else {
                throw new GlobalException(500, e.getMessage());
            }
        }
        CustomUser customUser = (CustomUser) authenticate.getPrincipal();
        redisTemplate.opsForValue()
                .set(Constants.LOGIN_USER_KEY + customUser.getUsername(), JSON.toJSONString(customUser), 60, TimeUnit.MINUTES);

        loginLogService.recordLoginLog(customUser.getUsername(), 1, "127.0.0.1", "登录成功");

        SysUser userInfo = customUser.getSysUser();
        String token = JwtHelper.createToken(userInfo.getId().toString(), userInfo.getUsername());
        UserInfoVO userInfoVO = UserInfoVO.builder()
                .id(userInfo.getId())
                .avatar(userInfo.getHeadUrl())
                .nickname(userInfo.getUsername())
                .email(userInfo.getUsername())
                .token(token).build();

        return Result.ok(userInfoVO);
    }

    @Override
    public Result selectUserInfoByToken(String token) {
        Map<String, Object> user = JwtHelper.getUser(token);
        if (user.isEmpty()) throw new GlobalException(404, "无效的token!");

        UserInfoVO userInfoVO = baseMapper.selectInfoByUserId(user.get("userId"));

        return Result.ok(userInfoVO);
    }

    @Override
    public void authLogin(AuthResponse response, String source, HttpServletResponse httpServletResponse) throws IOException {
        String result = JSONObject.toJSONString(response.getData());
        log.info("第三方登录验证结果:{}", result);

        JSONObject jsonObject = JSON.parseObject(result);
        Object uuid = jsonObject.get("uuid");
        // 获取用户ip信息
        String ipAddress = IpUtil.getIp();
//        String ipSource = IpUtil.getIp2region(ipAddress);

        SysUser user = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, uuid));
        if (null != user) {
            // 更新登录信息
            baseMapper.update(new SysUser(), new LambdaUpdateWrapper<SysUser>()
                    .set(SysUser::getUpdateTime, LocalDateTime.now())
                    .set(SysUser::getIpAddress, jsonObject.get("avatar"))
                    .set(SysUser::getHeadUrl, ipAddress)
//                    .set(SysUser::getIpSource, ipSource)F
                    .eq(SysUser::getId, user.getId()));
        } else {
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            // 获取第三方用户信息，保存到数据库返回 保存用户信息
            user = SysUser.builder()
                    .username(uuid.toString())
                    .password(encoder.encode("1"))
                    .name(source.equals("github") ? jsonObject.get("username").toString() : jsonObject.get("nickname").toString())
                    .headUrl(jsonObject.get("avatar").toString())
//                    .loginType(LoginTypeEnum.getType(source))
//                    .lastLoginTime(com.shiyi.utils.DateUtil.getNowDate())
                    .ipAddress(ipAddress)
//                    .ipSource(ipSource)
//                    .roleId(2)
                    .status(1)
                    .build();
            baseMapper.insert(user);
        }
        EmailLoginDTO loginDTO = new EmailLoginDTO();
        loginDTO.setEmail(user.getUsername());
        loginDTO.setPassword("1");
        Result res = this.emailLogin(loginDTO);
        UserInfoVO userInfo = (UserInfoVO)res.getData();
        httpServletResponse.sendRedirect("http://localhost:88?token=" + userInfo.getToken());
    }

}
